#include <osc-control.hpp>
#include <esp301-control.h>
#include <iostream>
#include <cstring>
#include <fstream>
#include <ctime>

using namespace std;

/** Prompt the user whether to continue.
 *  This function ask the user to type 'y' or 'n'.
 *  The program continues only if the user types '1'.
 *  @return 1 if yes; 0 if no.
 */
bool
yes_no_question ()
{
  char checkwarn = 'n';

//Loop until user correctly entered '0' or '1'.
  do
    {
      cout << "Do you wish to continue? [y/n] ";
      cin >> checkwarn;
    }
  while (checkwarn != 'y' && checkwarn != 'n');

  if (checkwarn == 'y')
    return 1;

  // Default return false
  return 0;
}

/** Input displacement parameters.
 *  This function ask the user to input the range and
 *  step precision of the displacement.
 *
 *  @retval start_displacement start displacement in millimeter.
 *      Warn the user if this is beyond the actuator range.
 *
 *  @retval step_displacement step displacement in millimeter.
 *      Warn the user if this is smaller than the repeable presision.
 *
 *  @retval no_of_line_scan number of line scans.
 *      Warn the user if this is larger than 1000.
 *
 *  @return 0 if any one of the parameter does not pass the test;
 *       1 if the user insists to continue.
 */
bool
input_displacement (float *start_displacement, float *step_displacement,
		    float *no_of_line_scan)
{
  cout << "Set initial position(mm): ";
  cin >> *start_displacement;
  if (*start_displacement < 0 || *start_displacement > 25)
    {
      cerr << endl << "Warning the initial position is outside range" << endl;
      if (!yes_no_question ())
	return 0;
    }

  cout << "Set relative displacement(mm): ";
  cin >> *step_displacement;
  if (*step_displacement < 0.0005 && *step_displacement > -0.0005)
    {
      cerr << endl <<
	"Warning the relative displacement is smaller than the minimum incremental motion by actuators"
	<< endl;
      if (!yes_no_question ())
	return 0;
    }

  cout << "No. of line scan: ";
  cin >> *no_of_line_scan;
  if (*no_of_line_scan > 1000 || *no_of_line_scan < 0)
    {
      cerr << endl << "Warning the input is outside the range " << endl;
      if (!yes_no_question ())
	return 0;
    }

  float total_displacement = (*no_of_line_scan) * (*step_displacement);
  cout << "Total displacement(mm): " << total_displacement;
  if ((total_displacement > 2) || (total_displacement < -2))
    {
      cerr << endl << "Warning the total displacement is more than 2mm" <<
	endl;
      if (!yes_no_question ())
	return 0;
    }

  //default return 1
  return 1;
}

/** Record and export trace data step by step.
 *  This function initiates trace acquisition for each
 *  displacement steps, and then save the acquired trace
 *  to the last line of the comma-separated file.
 *
 *  @param Agilent_DSO90k pointer to the osc_control library
 *  @param Motion_controller pointer to the esp301_control library
 *
 *  @param Array_length number of datapoints per trace.
 *  @param start_displacement start displacement in millimeter.
 *  @param step_displacement step displacement in millimeter.
 *  @param no_of_line_scan number of line scans.
 */
void
batch_record (osc_control * Agilent_DSO90k,
	      esp301_control * Motion_controller, unsigned Array_length,
	      float start_displacement, float step_displacement,
	      float no_of_line_scan)
{
  /** The filename is generated on-the-fly by
   *  formatting current datetime to 'yyyy-mm-ddThhmmss.csv'.
   *  This is to ensure the file will not be overwritten by
   *  next acquisition.
   */
  char filename[50];

  time_t rawtime;
  time (&rawtime);
  strftime (filename, 50, "%Y-%m-%dT%H%M%S.csv", localtime (&rawtime));

  // open a csv file on the hard disk
  fstream outfile;
  outfile.open (filename, fstream::out);

  // create a data buffer in memory
  int8_t *Data = new int8_t[Array_length];

  for (unsigned line_no = 1; line_no <= no_of_line_scan; line_no++)
    {

      // Get a single trace from the oscilloscope
      Agilent_DSO90k->getTrace8 (Data, Array_length);

      // Exporting data to hard disk
      cout << "Exporting data (line" << line_no << " )... ";
      for (unsigned idx = 0; idx < Array_length; idx++)
	outfile << (int) Data[idx] << ',';
      outfile << '\n';

      // Notify the user this line is saved to disk
      cout << "done" << endl;

      // Move to next position
      Motion_controller->moveTo (start_displacement +
				 line_no * step_displacement);
      /* cout << "Current position:" << Motion_controller->getPosition() << endl; */
    }

  // Close the file
  outfile.close ();

  // free up buffer from memory.
  delete[]Data;
}

int
main ()
{
  try
  {
//===================================================
// Step 1: Connecting to oscilloscope

    cout << "Connecting to Oscilloscope: ";
    osc_control Agilent_DSO90k;
    cout << "OK" << endl;

    cout << "Test if oscilloscope is ready: ";
    if (Agilent_DSO90k.testReady ())
      cout << "OK" << endl;
    else
      {
	cout << "FAIL" << endl;
	return 1;
      }

    cout << "Identify oscilloscope: ";
    if (Agilent_DSO90k.identify ())
      cout << "OK" << endl;
    else
      {
	cout << "FAIL" << endl;
	return 1;
      }

//===================================================
// Step 2: Connecting to motion controller

    cout << "Connecting to motion controller ";
    esp301_control Motion_controller;
    cout << "OK" << endl;

    cout << "Test if motion controller is ready: ";
    if (Motion_controller.connect ())
      cout << "OK" << endl;
    else
      {
	cout << "FAIL" << endl;
	return 1;
      }

//===================================================
// Step 3: Ask for displacement information

    cout << "Initilize motion controller:" << endl;
    float start_displacement;
    float step_displacement;
    float no_of_line_scan;

    bool success = input_displacement (&start_displacement,
				       &step_displacement, &no_of_line_scan);
    if (!success)
      {
	return 1;
      }

//===================================================
// Step 4: Preset the two machines

    cout << "Preset machines: " << endl;

    // Preset oscilloscope
    if (Agilent_DSO90k.preset ())
      cout << "Oscilloscope OK." << endl;
    else
      {
	cout << "Oscilloscope FAIL." << endl;
	return 1;
      }

    // Move to start position
    if (Motion_controller.moveTo (start_displacement))
      cout << "Motion contoller OK." << endl;
    else
      {
	cout << "Motion contoller FAIL." << endl;
	return 1;
      }

    // Get data length of traces
    cout << "Get number of datapoints per trace: ";
    unsigned Array_length = Agilent_DSO90k.number_of_points ();
    cout << Array_length << " OK" << endl;

//===================================================
// Step 5: Start acquistion.

    cout << "Exporting data..." << endl;
    batch_record (&Agilent_DSO90k, &Motion_controller,
		  Array_length, start_displacement,
		  step_displacement, no_of_line_scan);

//===================================================
// Catch any exception during acquition
  }
  catch (std::string e)
  {
    cout << "Oscilloscope error: " << e << endl;
  }
  catch (const char *e)
  {
    cout << "Motion controller error: " << e << endl;
  }

  system ("pause");
  return 0;
}
